#include "mysql_database.h"
#include "exceptions.h"
#include "watch.h"
#include "str.h"

void mysql_database::connect(const string &connection_string, const string &protocol_file /*= global_empty_string*/) {
  throw not_supported_exception("This method is currently not supported! try calling 'connect(const string &host, const string &user_id, const string &password, const string &db_name, const string &protocol_file = global_empty_string)'.");
} // mysql_database::connect

void mysql_database::connect(const string &host, const string &user_id, const string &password, const string &db_name, const string &protocol_file /*= global_empty_string*/) { // may throw a db_exception
  watch w(true);

  if (connected == true)
    disconnect();

  protocol = protocol_file;

  if (mysql_init(&mysql) == 0)
    throw db_exception(mysql_error(&mysql));

  if (mysql_real_connect(&mysql, host.c_str(), user_id.c_str(), password.c_str(), db_name.c_str(), 0, 0, 0) == 0)
    throw db_exception(mysql_error(&mysql));

  if (mysql_set_character_set(&mysql, "utf8") != 0)
    throw db_exception(mysql_error(&mysql));

  connected = true;
  prot("CON", "mysql_database", w.get_elapsed_milliseconds());
} // mysql_database::connect

void mysql_database::disconnect() {
  if (connected == true) {
    watch w(true);

    rollback();
    mysql_close(&mysql);
    connected = false;

    prot("DCO", "mysql_database", w.get_elapsed_milliseconds());
  } // if
} // mysql_database::disconnect

bool mysql_database::is_connected() {
  if (connected == false)
    return false;

  if (mysql_real_query(&mysql, "SELECT VERSION();", strlen("SELECT VERSION();")) != 0) // mysql_ping may cause a reconnect, we do not want that here
    connected = false;

  MYSQL_RES *result = mysql_store_result(&mysql); // this also closes the current command, required to avoid "Commands out of sync;"
  mysql_free_result(result);
  return connected;
} // mysql_database::is_connected

void mysql_database::execute_query(const string &s, record_set &rs, bool append_to_record_set) { // may throw a db_exception
  watch w(true);

  if (connected == false)
    throw db_exception(mysql_error(&mysql));

  if (mysql_real_query(&mysql, s.c_str(), s.length()) != 0) {
    const string msg = mysql_error(&mysql);

    rollback();
    throw db_exception(msg);
  } // if

  MYSQL_RES *result = mysql_store_result(&mysql); // gets the result-set
  MYSQL_ROW record;

  const unsigned int total_rows = (unsigned int) mysql_num_rows(result); // we do not need unsigned long long for number of rows
  const unsigned int total_columns = mysql_num_fields(result);

  if (append_to_record_set == false) {
    rs.reconstruct(total_rows, total_columns);

    // fills the record_set with column names
    const MYSQL_FIELD *fields = mysql_fetch_fields(result);
    for (unsigned int i = 0; i < total_columns; ++i) {
      const char * const column_name = fields[i].name;
      rs.fill_record_set(column_name, strlen(column_name));
    } // for i
  } else if (rs.get_column_count() == total_columns)
    rs.reserve(rs.get_row_count() + total_rows, total_columns);
  else
    throw db_exception("Number of columns are not the same! (append_to_record_set = true)");

  // fills the record_set with data
  for (unsigned long long row = 0; row < total_rows; ++row) {
    record = mysql_fetch_row(result); // fetches the next row
    if (record == 0)
      break; // end of records reached or an error occurred

    const unsigned long * const value_lengths = mysql_fetch_lengths(result);
    for (unsigned long long column = 0; column < total_columns; ++column)
      rs.fill_record_set(record[column], value_lengths[column]);

  } // for row

  // check for error(s)
  if (mysql_eof(result) == 0) {
    const string msg = mysql_error(&mysql);

    mysql_free_result(result); // free the results first
    rollback();
    throw db_exception(msg);
  } // if

  mysql_free_result(result);

  prot("QRY", s, w.get_elapsed_milliseconds(), rs.get_row_count());
} // mysql_database::execute_query

void mysql_database::execute_update(const string &s) {
  watch w(true);

  if (connected == false)
    throw db_exception(mysql_error(&mysql));

  if (mysql_real_query(&mysql, s.c_str(), s.length()) != 0) {
    const string msg = mysql_error(&mysql);
    rollback();
    throw db_exception(msg);
  } // if

  prot("UPD", s, w.get_elapsed_milliseconds());
} // mysql_database::execute_update

void mysql_database::start_transaction() {
  if (is_in_transaction == false) {
    watch w(true);

    is_in_transaction = true;
    if (mysql_autocommit(&mysql, 0) != 0) // makes sure that the autocommit is off
      throw db_exception(mysql_error(&mysql));

    prot("TRA", "STARTED", w.get_elapsed_milliseconds());
  } // if
} // mysql_database::start_transaction

void mysql_database::commit() {
  if (is_in_transaction == true) {
    watch w(true);
    is_in_transaction = false;

    if (mysql_commit(&mysql) != 0)
      throw db_exception(mysql_error(&mysql));

    if (mysql_autocommit(&mysql, 1) != 0) // makes sure that the autocommit is on
      throw db_exception(mysql_error(&mysql));

    prot("TRA", "COMMIT", w.get_elapsed_milliseconds());
  } // if
} // mysql_database::commit

void mysql_database::rollback() {
  if (is_in_transaction == true) {
    watch w(true);
    is_in_transaction = false;

    if (mysql_rollback(&mysql) != 0)
      throw db_exception(mysql_error(&mysql));

    if (mysql_autocommit(&mysql, 1) != 0) // makes sure that the autocommit is on
      throw db_exception(mysql_error(&mysql));

    prot("TRA", "ROLLBACK", w.get_elapsed_milliseconds());
  } // if
} // mysql_database::rollback

void mysql_database::lock_table(const string &table_name) {
  watch w(true);
  if (table_name.empty() == true)
    throw parameter_exception("table_name parameter is required to lock the table!");

  string s = "LOCK TABLE %1 WRITE;";
  str::format(s, arg<string>(table_name));
  execute_update(s);
  s = "TABLE %1 LOCKED";
  str::format(s, arg<string>(table_name));
  prot("LCK", s, w.get_elapsed_milliseconds());
} // mysql_database::lock_table

void mysql_database::unlock_table(const string &table_name) {
  watch w(true);
  execute_update("UNLOCK TABLES;");
  string s = "TABLE %1 UNLOCKED";
  str::format(s, arg<string>(table_name));
  prot("LCK", s, w.get_elapsed_milliseconds());
} // mysql_database::unlock_table

// constructor(s)
mysql_database::mysql_database() : database() {
  if (mysql_library_init(0, 0, 0) != 0) // initializes the mysql library
    throw db_exception(mysql_error(&mysql));
} // mysql_database::mysql_database

// destructor
mysql_database::~mysql_database() {
  disconnect();
  mysql_library_end();
} // mysql_database::~mysql_database
